package com.amadeus.commons.log.parser;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.channels.ByteChannel;
import java.nio.channels.Channels;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.amadeus.commons.log.config.AllProperties;
import com.amadeus.commons.log.dao.DAOFactory;
import com.amadeus.commons.log.dao.ErrorInstanceDAO;
import com.amadeus.commons.log.dao.ExceptionInstanceDAO;
import com.amadeus.commons.log.entity.ErrorInstance;
import com.amadeus.commons.log.entity.ExceptionInstance;
import com.amadeus.commons.log.entity.ExceptionStackTrace;
import com.amadeus.commons.log.entity.ExceptionType;

public class SystemLogParser {

	private Date lastStoredDate;
	private BlockingDeque<String> blockingDeque;
	private static PrintStream psError;
	private static PrintStream psException;
	private static final Pattern exceptionTypePattern = Pattern.compile("(\\w+\\.)+\\w*Exception");
	private static final Pattern jsiPattern = Pattern.compile("\\w+[!-]+\\d+[!-]+\\d+[!-]+\\d+");
	private static final Pattern callStackPattern = Pattern.compile("(\\s+at\\s\\w+\\.\\w+\\..+\r?\n){6}?");
	private static final ExceptionInstanceDAO exceptionInstanceDAO;
	private static final ErrorInstanceDAO errorInstanceDAO;

	private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
		@Override
		protected DateFormat initialValue() {
			DateFormat dateFormat = new SimpleDateFormat("MMM dd, yyyy hh:mm:ss a z");
			dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
			return dateFormat;
		}
	};

	static {
		try {
			psError = new PrintStream(new File(AllProperties.getInstance().getProperty("errors.out.file")));
			psException = new PrintStream(new File(AllProperties.getInstance().getProperty("exceptions.out.file")));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	static {
		DAOFactory daoFactory = DAOFactory.getDAOFactory(DAOFactory.MONGO);
		exceptionInstanceDAO = daoFactory.getExceptionInstanceDAO();
		errorInstanceDAO = daoFactory.getErrorInstanceDAO();
	}

	public SystemLogParser() {
		blockingDeque = new LinkedBlockingDeque<String>(4);
	}

	public void parse(ByteChannel byteChannel) {

		BufferedReader reader = new BufferedReader(new InputStreamReader(Channels.newInputStream(byteChannel)));

		try {
			String line = null;
			while ((line = reader.readLine()) != null) {
				storeLine(line);
				if (line.startsWith("####")) {
					if (line.contains("<Error>")) {
						error(line, reader);
					}
					storeDate(line);
				}
			}
			reader = null;
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	private void error(String line, BufferedReader reader) {
		storeDate(line);
		String next = null;
		String text = line;
		try {
			next = reader.readLine();
			while (next != null && !next.startsWith("####")) {
				text = text + "\n" + next;
				Matcher m = exceptionTypePattern.matcher(next);
				if (m.find()) {
					exception(text, reader);
					return;
				}
				next = reader.readLine();
			}
			psError.println(text);
			processError(text);
			if (next == null) {
				return;
			}
			if (next.contains("<Error>")) {
				error(next, reader);
			} else {
				storeDate(next);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void processError(String text) {
		Matcher m = jsiPattern.matcher(text);
		String jsi = null;
		if (m.find()) {
			jsi = m.group();
		}
		ErrorInstance errorInstance = new ErrorInstance();
		errorInstance.setDate(lastStoredDate);
		errorInstance.setErrorMessage(text);
		errorInstance.setJsi(jsi);
		errorInstanceDAO.insert(errorInstance);
	}

	private void exception(String text, BufferedReader reader) {
		storeDate(text);
		String next = null;
		try {
			next = reader.readLine();
			while (next != null && !next.startsWith("####")) {
				text = text + "\n" + next;
				next = reader.readLine();
			}
			psException.println(text);
			processException(text);
			if (next == null) {
				return;
			}
			if (next.contains("<Error>")) {
				error(next, reader);
			} else {
				storeDate(next);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void processException(String text) {
		Matcher m = jsiPattern.matcher(text);
		String jsi = null;
		if (m.find()) {
			jsi = m.group();
		}
		m = callStackPattern.matcher(text);
		String callStackKey = "";
		if (m.find()) {
			callStackKey = m.group();
			callStackKey = callStackKey.replaceAll("[\\d\\p{Punct}\\s]+", "");
			if (callStackKey.length() > 256) {
				callStackKey = callStackKey.substring(0, 256);
			}
			callStackKey = callStackKey.toLowerCase();
		}
		int hashCode = callStackKey.hashCode();
		psException.println(hashCode);
		m = exceptionTypePattern.matcher(text);
		if (m.find()) {
			ExceptionType et = new ExceptionType(m.group());
			ExceptionStackTrace est = new ExceptionStackTrace(hashCode, text.substring(text.indexOf('\n') + 1));
			ExceptionInstance ei = new ExceptionInstance(et, est, jsi, lastStoredDate);
			exceptionInstanceDAO.insert(ei);
		}
	}

	private void storeLine(String line) {
		while (!blockingDeque.offerFirst(line)) {
			blockingDeque.removeLast();
		}
	}

	private void storeDate(String line) {
		try {
			String date = line.substring(line.indexOf('<') + 1, line.indexOf('>'));
			lastStoredDate = df.get().parse(date);
		} catch (ParseException e) {
			e.printStackTrace();
		}
	}

}
